namespace Angelus.Data.Linq.OracleClient
{
	using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.OracleClient;
    using System.Data.DLinq.Provider;
    using System.Data.DLinq.ProviderBase;
    using System.Text;
    using Angelus.Data.Linq.ProviderBase;

    /// <summary>
    /// 
    /// </summary>
    internal class OracleStoredProcedureResultImpl
    {
        #region Fields

        private readonly OracleCommand _command;
        private readonly OracleContext _context;
        private readonly MetaFunction _function;
        private readonly bool _isScalar;
        private readonly List<IDelayDisposable> _readers;
        private OracleCompletingRowReader _completingReader;
        private IDataReader _reader;
        private int _resultSet;

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="command"></param>
        /// <param name="function"></param>
        /// <param name="isScalar"></param>
        internal OracleStoredProcedureResultImpl(OracleContext context, OracleCommand command, MetaFunction function,
                                                 bool isScalar)
        {
            this._context = context;
            this._command = command;
            this._function = function;
            this._isScalar = isScalar;

            this._readers = new List<IDelayDisposable>();
        }

        #endregion

        #region Dispose Implementation

        /// <summary>
        /// 
        /// </summary>
        internal void Dispose()
        {
            this.Reset();
        }

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        private bool NeedToExecute
        {
            get
            {
                if (null == this._reader)
                {
                    return true;
                }
                if (this._reader.IsClosed)
                {
                    return (!(this._completingReader.IsBuffered));
                }
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal int? ReturnValue
        {
            get
            {
                object value = this.GetParameterValue("RETURN_VALUE");
                int? returnValue = new int?();
                if (null != value)
                {
                    returnValue = Convert.ToInt32(value);
                }
                return returnValue;
            }
        }

        #endregion

        #region Stored Procedure Execution

        /// <summary>
        /// 
        /// </summary>
        internal void ExecuteStoredProcedure()
        {
            try
            {
                this.Reset();
                OracleConnection connection =
                    (OracleConnection) this._context.ConnectionManager.UseConnection(this._context);
                this._command.Connection = connection;
                this._command.Transaction = (OracleTransaction) this._context.LocalTransaction;
                this.LogExecution();
                if (!(this._isScalar))
                {
                    this._reader = this._command.ExecuteReader();
                    this._completingReader = new OracleCompletingRowReader(this._context.ConnectionManager,
                                                                           new DataRowReader(this._reader));
                    this._context.ConnectionManager.UseConnection(this._completingReader);
                }
                else
                {
                    this._command.ExecuteNonQuery();
                }
            }
            finally
            {
                this._context.ConnectionManager.ReleaseConnection(this._context);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parameterIndex"></param>
        /// <returns></returns>
        internal object GetParameterValue(int parameterIndex)
        {
            return this.GetParameterValue(this._command.Parameters[parameterIndex].ParameterName);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="parameterName"></param>
        /// <returns></returns>
        internal object GetParameterValue(string parameterName)
        {
            this.Buffer();
            if (null == parameterName)
            {
                throw new ArgumentNullException("parameterName");
            }
            if (0 == parameterName.Length)
            {
                throw new ArgumentException("The parameter name cannot be empty.", "parameterName");
            }

            OracleParameter parameter = this._command.Parameters[parameterName];
            if ((null != parameter) && (DBNull.Value != parameter.Value))
            {
                return parameter.Value;
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        internal IEnumerable<T> GetResults<T>()
        {
            return this.GetResults<T>(true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="multipleResult"></param>
        /// <returns></returns>
        internal IEnumerable<T> GetResults<T>(bool multipleResult)
        {
            return new EnumerableStoredProcedureResults<T>(this, multipleResult);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="multipleResult"></param>
        /// <returns></returns>
        internal IEnumerator<T> GetResultsImpl<T>(bool multipleResult)
        {
            if (this.NeedToExecute)
            {
                this.ExecuteStoredProcedure();
            }
            else if ((this._resultSet > 0) && (!(this._completingReader.NextResult())))
            {
                return null;
            }
            this._resultSet++;
            MetaType type = this._function.GetMultipleResultsRowType(typeof (T));
            if (null == type)
            {
                throw new InvalidOperationException(string.Format("Result type '{0}' is not mapped to function '{1}'.",
                                                                  typeof (T), this._function.Name));
            }
            IEnumerator<T> enumerator = (IEnumerator<T>) this._context.Translate(type, this._completingReader);
            if (multipleResult)
            {
                IDelayDisposable disposable = enumerator as IDelayDisposable;
                disposable.DelayDispose = true;
                this._readers.Add(disposable);
            }
            return enumerator;
        }

        /// <summary>
        /// 
        /// </summary>
        internal void LogExecution()
        {
            if (null != this._context.Log)
            {
                StringBuilder sb = new StringBuilder();
                foreach (OracleParameter parameter in this._command.Parameters)
                {
                    if (parameter.Direction != ParameterDirection.ReturnValue)
                    {
                        sb.Append(string.Format("{0}={1}", parameter,
                                                (parameter.Value == DBNull.Value) ? "NULL" : parameter.Value.ToString()));
                    }
                }
                string log = sb.ToString();
                log = log.TrimEnd(new char[] {',', ' '});
                this._context.Log.WriteLine(string.Format("Execute stored procedure: {0}({1})",
                                                          this._command.CommandText, log));
                this._context.Log.WriteLine();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal void Reset()
        {
            foreach (IDelayDisposable disposable in this._readers)
            {
                disposable.DelayDispose = false;
                disposable.Dispose();
            }
            this._readers.Clear();
            if (null != this._reader)
            {
                if (!(this._reader.IsClosed))
                {
                    this._reader.Close();
                }
                this._reader.Dispose();
                this._reader = null;
            }
            if (null != this._command)
            {
                this._command.Dispose();
            }
            if (null != this._completingReader)
            {
                this._context.ConnectionManager.ReleaseConnection(this._completingReader);
                this._completingReader.Dispose();
            }
            this._resultSet = 0;
        }

        #endregion

        #region Private Implementation

        /// <summary>
        /// 
        /// </summary>
        private void Buffer()
        {
            if (!(this._isScalar))
            {
                if (this.NeedToExecute)
                {
                    this.ExecuteStoredProcedure();
                }
                if (!(this.NeedToExecute) && (!(this._completingReader.IsBuffered)))
                {
                    ((IConnectionUser) this._completingReader).CompleteUse();
                }
            }
        }

        #endregion
    }
}